From 4f3e85c6f129199a432ef4ed3edbb667a5dedcc1 Mon Sep 17 00:00:00 2001
From: Jonathan Bell <jonathan@raspberrypi.com>
Date: Thu, 1 Dec 2022 16:59:44 +0000
Subject: [PATCH] usb: xhci: add XHCI_VLI_HUB_TT_QUIRK

The integrated USB2.0 hub in the VL805 chipset has a bug where it
incorrectly determines the remaining available frame time before the
host next sends a SOF packet with an incremented frame_number.

See the USB2.0 specification sections 11.3 and 11.14.2.3.

The hub's non-periodic TT handler can transmit the IN/OUT handshake
token too late, so a following 64-byte DATA0/1 packet causes the ACK
handshake to collide with the propagated SOF. This causes port babble.

Avoid ringing doorbells for vulnerable endpoints during uFrame 7 if the
TR is Idle to stop one source of babble. An IN transfer for a Running TR
may happen at any time, so there's not much we can do about that.

Ideally a hub firmware update to properly implement frame timeouts is
needed, and to avoid spinning for up to 125us when submitting TDs to
Idle rings.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
---
 drivers/usb/host/xhci-pci.c  |  1 +
 drivers/usb/host/xhci-ring.c | 46 ++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci.h      |  1 +
 3 files changed, 48 insertions(+)

--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -298,6 +298,7 @@ static void xhci_pci_quirks(struct devic
 		xhci->quirks |= XHCI_AVOID_DQ_ON_LINK;
 		xhci->quirks |= XHCI_VLI_TRB_CACHE_BUG;
 		xhci->quirks |= XHCI_VLI_SS_BULK_OUT_BUG;
+		xhci->quirks |= XHCI_VLI_HUB_TT_QUIRK;
 	}
 
 	if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA &&
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -3532,6 +3532,48 @@ static int xhci_align_td(struct xhci_hcd
 	return 1;
 }
 
+static void xhci_vl805_hub_tt_quirk(struct xhci_hcd *xhci, struct urb *urb,
+				    struct xhci_ring *ring)
+{
+	struct list_head *tmp;
+	struct usb_device *udev = urb->dev;
+	unsigned int timeout = 0;
+	unsigned int single_td = 0;
+
+	/*
+	 * Adding a TD to an Idle ring for a FS nonperiodic endpoint
+	 * that is behind the internal hub's TT will run the risk of causing a
+	 * downstream port babble if submitted late in uFrame 7.
+	 * Wait until we've moved on into at least uFrame 0
+	 * (MFINDEX references the next SOF to be transmitted).
+	 *
+	 * Rings for IN endpoints in the Running state also risk causing
+	 * babble if the returned data is large, but there's not much we can do
+	 * about it here.
+	 */
+	if (udev->route & 0xffff0 || udev->speed != USB_SPEED_FULL)
+		return;
+
+	list_for_each(tmp, &ring->td_list) {
+		single_td++;
+		if (single_td == 2) {
+			single_td = 0;
+			break;
+		}
+	}
+	if (single_td) {
+		while (timeout < 20 &&
+		       (readl(&xhci->run_regs->microframe_index) & 0x7) == 0) {
+			udelay(10);
+			timeout++;
+		}
+		if (timeout >= 20)
+			xhci_warn(xhci, "MFINDEX didn't advance - %u.%u dodged\n",
+				  readl(&xhci->run_regs->microframe_index) >> 3,
+				  readl(&xhci->run_regs->microframe_index) & 7);
+	}
+}
+
 /* This is very similar to what ehci-q.c qtd_fill() does */
 int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 		struct urb *urb, int slot_id, unsigned int ep_index)
@@ -3700,6 +3742,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *
 	}
 
 	check_trb_math(urb, enqd_len);
+	if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK)
+		xhci_vl805_hub_tt_quirk(xhci, urb, ring);
 	giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
 			start_cycle, start_trb);
 	return 0;
@@ -3835,6 +3879,8 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *
 			/* Event on completion */
 			field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state);
 
+	if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK)
+		xhci_vl805_hub_tt_quirk(xhci, urb, ep_ring);
 	giveback_first_trb(xhci, slot_id, ep_index, 0,
 			start_cycle, start_trb);
 	return 0;
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1905,6 +1905,7 @@ struct xhci_hcd {
 #define XHCI_AVOID_DQ_ON_LINK  BIT_ULL(47)
 #define XHCI_VLI_TRB_CACHE_BUG BIT_ULL(48)
 #define XHCI_VLI_SS_BULK_OUT_BUG       BIT_ULL(49)
+#define XHCI_VLI_HUB_TT_QUIRK  BIT_ULL(50)
 
 	unsigned int		num_active_eps;
 	unsigned int		limit_active_eps;
